package c0907m.group2.aitsm.util;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.*;

public class AutoDateFileHandler extends StreamHandler {
    private boolean append;

    private String pattern = "yyyyMMdd_HHmm";
    private String lockFileName;
    private FileOutputStream lockStream;
    private File currentFile;
    private static final int MAX_LOCKS = 100;
    private static java.util.HashMap locks = new java.util.HashMap();
    private int lastRotateTime;
    private int rotateTimeField = Calendar.MINUTE;
    private Calendar logCal = Calendar.getInstance();

    private void open(File fname, boolean append) throws IOException {
        int len = 0;
        if (append) {
            len = (int) fname.length();
        }
        FileOutputStream fout = new FileOutputStream(fname.toString(), append);
        BufferedOutputStream bout = new BufferedOutputStream(fout);
        setOutputStream(bout);
        lastRotateTime = logCal.get(rotateTimeField);
    }


    /**
     * Construct a default <tt>FileHandler</tt>.  This will be configured
     * entirely from <tt>LogManager</tt> properties (or their default values).
     * <p/>
     *
     * @throws java.io.IOException  if there are IO problems opening the currentFile.
     * @throws SecurityException    if a security manager exists and if
     *                              the caller does not have <tt>LoggingPermission("control"))</tt>.
     * @throws NullPointerException if pattern property is an empty String.
     */
    public AutoDateFileHandler() throws IOException {
        openFiles();
    }

    /**
     * Initialize a <tt>FileHandler</tt> to write to the given filename.
     * <p/>
     * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
     * properties (or their default values) except that the given pattern
     * argument is used as the filename pattern, the file limit is
     * set to no limit, and the file count is set to one.
     * <p/>
     * There is no limit on the amount of data that may be written,
     * so use this with care.
     *
     * @param pattern the name of the output file
     * @throws java.io.IOException      if there are IO problems opening the currentFile.
     * @throws SecurityException        if a security manager exists and if
     *                                  the caller does not have <tt>LoggingPermission("control")</tt>.
     * @throws IllegalArgumentException if pattern is an empty string
     */
    public AutoDateFileHandler(String pattern,
                               int rotateTimeField, boolean append) throws IOException,
            SecurityException {
        this.append = append;
        this.rotateTimeField = rotateTimeField;
        if (pattern.length() < 1) {
            throw new IllegalArgumentException();
        }
        this.pattern = pattern;
        openFiles();
    }

    /**
     * Initialize a <tt>FileHandler</tt> to write to the given filename,
     * with optional append.
     * <p/>
     * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
     * properties (or their default values) except that the given pattern
     * argument is used as the filename pattern, the file limit is
     * set to no limit, the file count is set to one, and the append
     * mode is set to the given <tt>append</tt> argument.
     * <p/>
     * There is no limit on the amount of data that may be written,
     * so use this with care.
     *
     * @param pattern the name of the output file
     * @param append  specifies append mode
     * @throws java.io.IOException      if there are IO problems opening the currentFile.
     * @throws SecurityException        if a security manager exists and if
     *                                  the caller does not have <tt>LoggingPermission("control")</tt>.
     * @throws IllegalArgumentException if pattern is an empty string
     */
    public AutoDateFileHandler(String pattern, boolean append,
                               int rotateTimeField) throws IOException,
            SecurityException {
        this.rotateTimeField = rotateTimeField;
        if (pattern.length() < 1) {
            throw new IllegalArgumentException();
        }
        this.pattern = pattern;
        this.append = append;
        openFiles();
    }

    // Private method to open the set of output files, based on the
    // configured instance variables.

    private void openFiles() throws IOException {
        LogManager manager = LogManager.getLogManager();
        manager.checkAccess();

        // We register our own ErrorManager during initialization
        // so we can record exceptions.
        InitializationErrorManager em = new InitializationErrorManager();
        setErrorManager(em);

        currentFile = generate(pattern);

        acquireLock(currentFile.toString());

        // Create the initial log file.
        if (append) {
            open(currentFile, true);
        } else {
            rotate();
        }

        // Did we detect any exceptions during initialization?
        Exception ex = em.lastException;
        if (ex != null) {
            if (ex instanceof IOException) {
                throw (IOException) ex;
            } else if (ex instanceof SecurityException) {
                throw (SecurityException) ex;
            } else {
                throw new IOException("Exception: " + ex);
            }
        }

        // Install the normal default ErrorManager.
        setErrorManager(new ErrorManager());
    }

    @SuppressWarnings("unchecked")
    private void acquireLock(String fileName) throws IOException {
        // Generate a lock file name from the "unique" int.
        this.lockFileName = fileName + ".lck";
        // Create a lock file.  This grants us exclusive access
        // to our set of output files, as long as we are alive.
        int unique = -1;
        for (; ;) {
            unique++;
            if (unique > MAX_LOCKS) {
                throw new IOException("Couldn't get lock for " + lockFileName);
            }
            // Now try to lock that filename.
            // Because some systems (e.g. Solaris) can only do file locks
            // between processes (and not within a process), we first check
            // if we ourself already have the file locked.
            synchronized (locks) {
                if (locks.get(lockFileName) != null) {
                    // We already own this lock, for a different FileHandler
                    // object.  Try again.
                    continue;
                }
                FileChannel fc;
                try {
                    lockStream = new FileOutputStream(lockFileName);
                    fc = lockStream.getChannel();
                } catch (IOException ix) {
                    // We got an IOException while trying to open the file.
                    // Try the next file.
                    continue;
                }
                try {
                    FileLock fl = fc.tryLock();
                    if (fl == null) {
                        // We failed to get the lock.  Try next file.
                        continue;
                    }
                    // We got the lock OK.
                } catch (IOException ix) {
                    // We got an IOException while trying to get the lock.
                    // This normally indicates that locking is not supported
                    // on the target directory.  We have to proceed without
                    // getting a lock.   Drop through.
                }
                // We got the lock.  Remember it.
                locks.put(lockFileName, lockFileName);
                break;
            }
        }
    }

    // Generate a filename from a pattern.

    private File generate(String pattern) throws IOException {
        File file = null;
        String name = new SimpleDateFormat(pattern).format(new Date());
        file = new File(name);
        return file;
    }

    // Rotate the set of output files

    private synchronized void rotate() {
        Level oldLevel = getLevel();
        setLevel(Level.OFF);

        //Close file and release lock
        this.close();
        try {
            currentFile = generate(pattern);
            acquireLock(currentFile.toString());
            open(currentFile, false);
        } catch (IOException ix) {
            // We don't want to throw an exception here, but we
            // report the exception to any registered ErrorManager.
            reportError(null, ix, ErrorManager.OPEN_FAILURE);

        }
        setLevel(oldLevel);
    }

    /**
     * Format and publish a <tt>LogRecord</tt>.
     *
     * @param record description of the log event. A null record is
     *               silently ignored and is not published
     */
    @SuppressWarnings("unchecked")
    public synchronized void publish(LogRecord record) {
        logCal.setTimeInMillis(record.getMillis());
        if (logCal.get(rotateTimeField) != lastRotateTime) {
            // We performed access checks in the "init" method to make sure
            // we are only initialized from trusted code.  So we assume
            // it is OK to write the target files, even if we are
            // currently being called from untrusted code.
            // So it is safe to raise privilege here.
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    rotate();
                    return null;
                }
            });
        }

        if (!isLoggable(record)) {
            return;
        }
        super.publish(record);
        flush();
    }

    /**
     * Close all the currentFile.
     *
     * @throws SecurityException if a security manager exists and if
     *                           the caller does not have <tt>LoggingPermission("control")</tt>.
     */
    public synchronized void close() throws SecurityException {
        super.close();
        // Unlock any lock file.
        if (lockFileName == null) {
            return;
        }
        try {
            // Closing the lock file's FileOutputStream will close
            // the underlying channel and free any locks.
            lockStream.close();
        } catch (Exception ex) {
            // Problems closing the stream.  Punt.
        }
        synchronized (locks) {
            locks.remove(lockFileName);
        }
        new File(lockFileName).delete();
        lockFileName = null;
        lockStream = null;
    }

    private static class InitializationErrorManager extends ErrorManager {
        Exception lastException;

        public void error(String msg, Exception ex, int code) {
            lastException = ex;
        }
    }
}
